#include "SocketEpollDemo.h"
#include <iostream>
#include <string>
#include <fcntl.h>

using namespace std;

#ifdef __linux__
#include "sys/epoll.h"
#include "ThreadPool.h"

using namespace common;

SOCKET createEpollService(int port)
{
    // 1. 创建 Socket
    SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (serverSocket == -1)
    {
        cerr << "创建失败！！" << endl;
        return -1;
    }

    // 2. bind IP端口
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(port);
    int bindRet = ::bind(serverSocket, (sockaddr *)&serverAddr, sizeof(serverAddr));
    if (bindRet == -1)
    {
        cerr << "绑定端口失败！！！" << endl;
        compatCloseSocket(serverSocket);
        return -1;
    }

    // 3. listen
    int listenRet = listen(serverSocket, 1024);
    if (listenRet == -1)
    {
        cerr << "监听失败！！！" << endl;
        compatCloseSocket(serverSocket);
        return -1;
    }

    return serverSocket;
}

// 水平触发LT
void simpleLtEpollDemo()
{
    SOCKET serverSocket = createEpollService(9999);

    // 创建 epoll
    int epollFd = epoll_create(1);
    if(epollFd == -1)
    {
        cerr << "epoll create 失败!!!" << endl;
        compatCloseSocket(serverSocket);
        return;
    }

    // 添加监听文件描述符 （SOCKET） 到 epoll
    epoll_event epollEvent;
    epollEvent.events = EPOLLIN;
    epollEvent.data.fd = serverSocket;
    epoll_ctl(epollFd, EPOLL_CTL_ADD, serverSocket, &epollEvent);

    epoll_event epollEvents[1024];
    int epollEventsLen = sizeof(epollEvents) / sizeof(epoll_event);
    while (true)
    {
        int count = epoll_wait(epollFd, epollEvents, epollEventsLen, -1);
        if (count == -1)
        {
            cerr << "epoll 异常" << endl;
            break;
        }
        cout << " epoll :: " << count << "/" << epollEventsLen << endl;
        for (int i = 0; i < count; ++i)
        {
            SOCKET currentSocket = epollEvents[i].data.fd;
            if (currentSocket == serverSocket)
            {
                sockaddr_in clientAddr;
                addrlen clientLen = sizeof(clientAddr);
                SOCKET clientSocket = accept(currentSocket, (sockaddr *)&clientAddr, &clientLen);
                if (clientSocket == -1)
                {
                    cerr << "接受客户链接失败！！！" << endl;
                    continue;
                }
                cout << "客户端IP：" << inet_ntoa(clientAddr.sin_addr) << "；端口：" << ntohs(clientAddr.sin_port) << "已连接" << endl;

                // 新得到的文件描述符添加到 epoll 模型中, 下一轮循环的时候就可以被检测了
                epollEvent.events = EPOLLIN;
                epollEvent.data.fd = clientSocket;
                int ctlRet = epoll_ctl(epollFd, EPOLL_CTL_ADD, clientSocket, &epollEvent);
                if(ctlRet == -1)
                {
                    cerr << "添加新客户链接到epoll失败！！" << endl;
                    compatCloseSocket(clientSocket);
                    continue;
                }
            }
            else
            {
                char readBuff[1024];
                int len = recv(currentSocket, readBuff, sizeof(readBuff), 0);
                if (len == -1)
                {
                    cerr << "读取客户端发送数据异常！！！" << endl;
                    continue;
                }
                else if (len == 0)
                {
                    cout << "客户端已断开链接！！" << endl;
                    // 一定要先删除在关闭，否则会抛异常删除失败。
                    epoll_ctl(epollFd, EPOLL_CTL_DEL, currentSocket, NULL);
                    compatCloseSocket(currentSocket);
                    continue;
                }

                // 读取成功
                cout << "服务器接受到数据: " << readBuff << endl;
                // 回复给客户端
                for (int i = 0; i < len; ++i)
                {
                    readBuff[i] = toupper(readBuff[i]);
                }
                int sendRet = send(currentSocket, readBuff, strlen(readBuff) + 1, 0);
                if (sendRet == -1)
                {
                    // 读取失败
                    cerr << "回复失败: " << endl;
                }
            }
        }
    }

    epoll_ctl(epollFd, EPOLL_CTL_DEL, serverSocket, NULL);
    compatCloseSocket(serverSocket);
}

// 边缘触发ET
void simpleEtEpollDemo()
{
    SOCKET serverSocket = createEpollService(9999);

    // 创建 epoll
    int epollFd = epoll_create(1);
    if(epollFd == -1)
    {
        cerr << "epoll create 失败!!!" << endl;
        compatCloseSocket(serverSocket);
        return;
    }

    // 添加监听文件描述符 （SOCKET） 到 epoll
    epoll_event epollEvent;
    epollEvent.events = EPOLLIN | EPOLLET;
    epollEvent.data.fd = serverSocket;
    epoll_ctl(epollFd, EPOLL_CTL_ADD, serverSocket, &epollEvent);

    epoll_event epollEvents[1024];
    int epollEventsLen = sizeof(epollEvents) / sizeof(epoll_event);
    while (true)
    {
        int count = epoll_wait(epollFd, epollEvents, epollEventsLen, -1);
        if (count == -1)
        {
            cerr << "epoll 异常" << endl;
            break;
        }
        
        cout << " epoll :: " << count << "/" << epollEventsLen << endl;
        
        for (int i = 0; i < count; ++i)
        {
            SOCKET currentSocket = epollEvents[i].data.fd;
            if (currentSocket == serverSocket)
            {
                sockaddr_in clientAddr;
                addrlen clientLen = sizeof(clientAddr);
                SOCKET clientSocket = accept(currentSocket, (sockaddr *)&clientAddr, &clientLen);
                if (clientSocket == -1)
                {
                    cerr << "接受客户链接失败！！！" << endl;
                    continue;
                }
                cout << "客户端IP：" << inet_ntoa(clientAddr.sin_addr) << "；端口：" << ntohs(clientAddr.sin_port) << "已连接" << endl;
                
                // 将文件描述符设置为非阻塞
                // 得到文件描述符的属性
                int flag = fcntl(clientSocket, F_GETFL);
                flag |= O_NONBLOCK;
                fcntl(clientSocket, F_SETFL, flag);

                // 新得到的文件描述符添加到 epoll 模型中, 下一轮循环的时候就可以被检测了
                // 通信的文件描述符检测读缓冲区数据的时候设置为边沿模式
                epollEvent.events = EPOLLIN | EPOLLET;  // 读缓冲区是否有数据
                epollEvent.data.fd = clientSocket;
                int ctlRet = epoll_ctl(epollFd, EPOLL_CTL_ADD, clientSocket, &epollEvent);
                if(ctlRet == -1)
                {
                    cerr << "添加新客户链接到epoll失败！！" << endl;
                    compatCloseSocket(clientSocket);
                    continue;
                }
            }
            else
            {
                string msg;
                while(true)
                {
                    // char readBuff[1024];
                    char readBuff[5];
                    int len = recv(currentSocket, readBuff, sizeof(readBuff), 0);

                    if (len == -1)
                    {
                        if(errno == EAGAIN)
                        {
                            cout << "读取数据完成！！！" << endl;
                        } 
                        else
                        {
                            cerr << "读取客户端发送数据异常！！！" << endl;
                        }
                        break;
                    }
                    else if (len == 0)
                    {
                        cout << "客户端已断开链接！！" << endl;
                        // 一定要先删除在关闭，否则会抛异常删除失败。
                        epoll_ctl(epollFd, EPOLL_CTL_DEL, currentSocket, NULL);
                        compatCloseSocket(currentSocket);
                        break;
                    }
                    
                    msg.append(readBuff);
                }

                if(msg.length() > 0)
                {
                    // 读取成功
                    cout << "服务器接受到数据: " << msg << endl;
                    // 回复给客户端
                    for (int i = 0; i < msg.length(); ++i)
                    {
                        msg[i] = toupper(msg[i]);
                    }
                    int sendRet = send(currentSocket, msg.c_str(), strlen(msg.c_str()) + 1, 0);
                    if (sendRet == -1)
                    {
                        // 读取失败
                        cerr << "回复失败: " << endl;
                    }
                }
            }
        }
    }

    epoll_ctl(epollFd, EPOLL_CTL_DEL, serverSocket, NULL);
    compatCloseSocket(serverSocket);
}

struct EpollInfo
{
    int epollFd;
    SOCKET currentSocket;
};

// 水平触发LT线程池版本
void threadLtEpollDemo()
{
    SOCKET serverSocket = createEpollService(9999);

    // 创建 epoll
    int epollFd = epoll_create(1);
    if(epollFd == -1)
    {
        cerr << "epoll create 失败!!!" << endl;
        compatCloseSocket(serverSocket);
        return;
    }

    // 添加监听文件描述符到 epoll
    epoll_event epollEvent;
    epollEvent.events = EPOLLIN;
    epollEvent.data.fd = serverSocket;
    epoll_ctl(epollFd, EPOLL_CTL_ADD, serverSocket, &epollEvent);

    ThreadPool threadPool(3, 10);
    
    TaskCallBack readCall = [](void *const data) -> void {
        EpollInfo* epollInfo = (EpollInfo*) data;

        char readBuff[1024];
        int len = recv(epollInfo->currentSocket, readBuff, sizeof(readBuff), 0);

        if (len == 0)
        {
            cout << "客户端已断开链接！！" << endl;
            // 一定要先删除在关闭，否则会抛异常删除失败。
            // 在进入多线程钱会取消对当前读取的通道的epoll检测避免不必要的线程开启
            // epoll_ctl(epollInfo->epollFd, EPOLL_CTL_DEL, epollInfo->currentSocket, NULL); 
            compatCloseSocket(epollInfo->currentSocket);
            return;
        }
        
        if (len > 0)
        {
            // 读取成功
            cout << "服务器接受到数据: " << readBuff << endl;
            // 回复给客户端
            for (int i = 0; i < len; ++i)
            {
                readBuff[i] = toupper(readBuff[i]);
            }
            int sendRet = send(epollInfo->currentSocket, readBuff, strlen(readBuff) + 1, 0);
            if (sendRet == -1)
            {
                // 读取失败
                cerr << "回复失败: " << endl;
            }
        }
        else 
        {
            cerr << "读取客户端发送数据异常！！！" << endl;
        }

        epoll_event epollEvent;
        epollEvent.events = EPOLLIN;
        epollEvent.data.fd = epollInfo->currentSocket;
        epoll_ctl(epollInfo->epollFd, EPOLL_CTL_ADD, epollInfo->currentSocket, &epollEvent);
    };


    epoll_event epollEvents[1024];
    int epollEventsLen = sizeof(epollEvents) / sizeof(epoll_event);
    while (true)
    {
        int count = epoll_wait(epollFd, epollEvents, epollEventsLen, -1);
        if(count == -1)
        {
            cerr << "epoll 异常" << endl;
            break;
        }
        
        for (int i = 0; i < count; ++i)
        {
            SOCKET currentSocket = epollEvents[i].data.fd;
            if (currentSocket == serverSocket)
            {
                sockaddr_in clientAddr;
                addrlen clientLen = sizeof(clientAddr);
                SOCKET clientSocket = accept(currentSocket, (sockaddr *)&clientAddr, &clientLen);
                if (clientSocket == -1)
                {
                    cerr << "接受客户链接失败！！！" << endl;
                    continue;
                }
                cout << "客户端IP：" << inet_ntoa(clientAddr.sin_addr) << "；端口：" << ntohs(clientAddr.sin_port) << "已连接" << endl;

                // 新得到的文件描述符添加到 epoll 模型中, 下一轮循环的时候就可以被检测了
                epollEvent.events = EPOLLIN;
                epollEvent.data.fd = clientSocket;
                int ctlRet = epoll_ctl(epollFd, EPOLL_CTL_ADD, clientSocket, &epollEvent);
                if(ctlRet == -1)
                {
                    cerr << "添加新客户链接到epoll失败！！" << endl;
                    compatCloseSocket(clientSocket);
                    continue;
                }
            }
            else
            {
                epoll_ctl(epollFd, EPOLL_CTL_DEL, currentSocket, NULL);

                EpollInfo* epollInfo = new EpollInfo();
                // EpollInfo* epollInfo = (EpollInfo*) malloc(sizeof(EpollInfo));
                epollInfo->epollFd = epollFd;
                epollInfo->currentSocket = currentSocket;
                threadPool.addTask(readCall, epollInfo, deleter<EpollInfo>);
                // threadPool.addTask(readCall, epollInfo, deleter<decltype(epollInfo)>);
            }
        }
    }

    epoll_ctl(epollFd, EPOLL_CTL_DEL, serverSocket, NULL);
    compatCloseSocket(serverSocket);
}

// 边缘触发ET 线程池版本
void threadEtEpollDemo()
{
    SOCKET serverSocket = createEpollService(9999);

    // 创建 epoll
    int epollFd = epoll_create(1);
    if(epollFd == -1)
    {
        cerr << "epoll create 失败!!!" << endl;
        compatCloseSocket(serverSocket);
        return;
    }

    // 添加监听文件描述符到 epoll
    epoll_event epollEvent;
    epollEvent.events = EPOLLIN;
    epollEvent.data.fd = serverSocket;
    epoll_ctl(epollFd, EPOLL_CTL_ADD, serverSocket, &epollEvent);

    ThreadPool threadPool(3, 10);

    TaskCallBack readCall = [](void *const data) -> void {
        EpollInfo* epollInfo = (EpollInfo*) data;

        string msg;
        while(true)
        {
            // char readBuff[1024];
            char readBuff[5];
            int len = recv(epollInfo->currentSocket, readBuff, sizeof(readBuff), 0);
            if (len == -1)
            {
                if(errno == EAGAIN)
                {
                    cout << "读取数据完成！！！" << endl;
                } 
                else
                {
                    cerr << "读取客户端发送数据异常！！！" << endl;
                }
                break;
            }
            else if (len == 0)
            {
                cout << "客户端已断开链接！！" << endl;
                // 一定要先删除在关闭，否则会抛异常删除失败。
                epoll_ctl(epollInfo->epollFd, EPOLL_CTL_DEL, epollInfo->currentSocket, NULL);
                compatCloseSocket(epollInfo->currentSocket);
                break;
            }
            
            msg.append(readBuff);
        }

        if(msg.length() > 0)
        {
            // 读取成功
            cout << "服务器接受到数据: " << msg << endl;
            // 回复给客户端
            for (int i = 0; i < msg.length(); ++i)
            {
                msg[i] = toupper(msg[i]);
            }
            int sendRet = send(epollInfo->currentSocket, msg.c_str(), strlen(msg.c_str()) + 1, 0);
            if (sendRet == -1)
            {
                // 读取失败
                cerr << "回复失败: " << endl;
            }
        }
    };


    epoll_event epollEvents[1024];
    int epollEventsLen = sizeof(epollEvents) / sizeof(epoll_event);
    while (true)
    {
        int count = epoll_wait(epollFd, epollEvents, epollEventsLen, -1);
        if(count == -1)
        {
            cerr << "epoll 异常" << endl;
            break;
        }
        
        for (int i = 0; i < count; ++i)
        {
            SOCKET currentSocket = epollEvents[i].data.fd;
            if (currentSocket == serverSocket)
            {
                sockaddr_in clientAddr;
                addrlen clientLen = sizeof(clientAddr);
                SOCKET clientSocket = accept(currentSocket, (sockaddr *)&clientAddr, &clientLen);
                if (clientSocket == -1)
                {
                    cerr << "接受客户链接失败！！！" << endl;
                    continue;
                }
                cout << "客户端IP：" << inet_ntoa(clientAddr.sin_addr) << "；端口：" << ntohs(clientAddr.sin_port) << "已连接" << endl;

                // 将文件描述符设置为非阻塞
                // 得到文件描述符的属性
                int flag = fcntl(clientSocket, F_GETFL);
                flag |= O_NONBLOCK;
                fcntl(clientSocket, F_SETFL, flag);

                // 新得到的文件描述符添加到 epoll 模型中, 下一轮循环的时候就可以被检测了
                epollEvent.events = EPOLLIN | EPOLLET;
                epollEvent.data.fd = clientSocket;
                int ctlRet = epoll_ctl(epollFd, EPOLL_CTL_ADD, clientSocket, &epollEvent);
                if(ctlRet == -1)
                {
                    cerr << "添加新客户链接到epoll失败！！" << endl;
                    compatCloseSocket(clientSocket);
                    continue;
                }
            }
            else
            {
                EpollInfo* epollInfo = new EpollInfo();
                epollInfo->epollFd = epollFd;
                epollInfo->currentSocket = currentSocket;
                threadPool.addTask(readCall, epollInfo, deleter<EpollInfo>);
            }
        }               
    }

    epoll_ctl(epollFd, EPOLL_CTL_DEL, serverSocket, NULL);
    compatCloseSocket(serverSocket);
}
#endif

void socketEpollDemo()
{
    // 对于 socket 并发编程存在多个阻塞点，这些组赛点相互互斥，导致需要使用多线程来解决，当并发两居高时会导致大量线程被开辟从而造成内存和系统资源开销影响服务器负荷。
    // 但其实中间有诸多客户端链接可能只是保持链接并没有过于频繁的操作，这样的线程开辟造成了资源的浪费，如果处理线程可以得到复用则会大大奖励这种负荷开销实现高性能并发。
    // 在 C++ socket 编程中提供了 select、poll、epoll 三种方式，其中，select、poll 内部使用链表维护，epoll 使用红黑树维护，所以：
    //  1. 在性能上 select、poll 要略逊于 epoll；
    //  2. 在可检测连接数上 select 也有限制，最大时 1024，超过 1024 就不行了（这个取决于系统配置），而 epoll、poll 没有这方面限制可以无限大；
    //  3. select 支持 windows 和 mac、linux；但 poll 和 epoll 并不支持 windows。
    // 注意：这里的可检测连接数并不是并发连接数，可以并发链接的数量取决于ip和端口组合数。这里是指在链接到服务器上的链接里去检测那些有传输数据那些没有。
    //      也就是说可以间接2000个客户端，但是超过1024后面的可能得不到检测。
    // 总结：所以我们需要着重理解 select 和 epoll 因为在 linux 下 epoll 有更好得优势，而考虑跨平台性时只能选择 select。

    // epoll 全称 eventpoll，是 linux 内核实现 IO 多路转接/复用（IO multiplexing）的一个实现。
    // IO 多路转接的意思是在一个操作里同时监听多个输入输出源，在其中一个或多个输入输出源可用的时候返回，然后对其的进行读写操作。
    // epoll 是 select 和 poll 的升级版，相较于这两个前辈，epoll 改进了工作方式，因此它更加高效。
    //  * 对于待检测集合 select 和 poll 是基于线性方式处理的，epoll 是基于红黑树来管理待检测集合的。
    //  * select 和 poll 每次都会线性扫描整个待检测集合，集合越大速度越慢，epoll 使用的是回调机制，效率高，处理效率也不会随着检测集合的变大而下降
    //    - select 和 poll 随着扫描集合越大性能越差，程曲线增长，在并发数量较少时有较好的性能，但扫描两增大后性能明显下降
    //    - epoll 不会随着检测数量增多而下降，性能相对固定，在数量少的时候也有一定开销可能比 select 或 poll 高，但是数量增多时也是一样的性能这是 select 和 poll 不能比的，
    //      所以海量并发时 epoll 要远高于 select 和 poll。
    //  * select 和 poll 工作过程中存在内核/用户空间数据的频繁拷贝问题，在 epoll 中内核和用户区使用的是共享内存（基于 mmap 内存映射区实现），省去了不必要的内存拷贝。
    //    - 大多数程序性能瓶颈均来自内存操作，过于频繁的内存拷贝操作会严重的影响性能（类似于 for 循环无间隔操作属于频繁操作），偶尔的内存操作其实并不影响（不必畏惧内存操作）。
    //  * 程序猿需要对 select 和 poll 返回的集合进行判断才能知道哪些文件描述符是就绪的，通过 epoll 可以直接得到已就绪的文件描述符集合，无需再次检测。
    //    - select 和 poll 都是经由一个文件描述符容器来作为传入传出参数获得就绪状态，虽然可以得知有多少已就绪的文件描述符，但是并不知道时容器中的那些，还需要迭代判定会有一定开销。
    //      epoll 是基于事件回调的不光知道数量还知道具体是哪些文件描述符就绪了，不需要再次循环遍历来确认状态。
    //  * 使用 epoll 没有最大文件描述符的限制，仅受系统中进程能打开的最大文件数目限制(既内存硬件限制，内存越大数量越多)。select 收到限制约束，默认为 1024，有内核程序写死，可以重编内核解决。
    //
    // 当多路复用的文件数量庞大、IO 流量频繁的时候，一般不太适合使用 select() 和 poll()，这种情况下 select() 和 poll() 表现较差，推荐使用 epoll()。反之则可以考虑 select() 和 poll()。
    //
    // 操作函数
    // select 或者 poll 低效的原因之一是将“添加/维护待检测任务”和“阻塞进程/线程”两个步骤合二为一。
    // 每次调用 select 都需要这两步操作，然而大多数应用场景中，需要监视的 socket 个数相对固定，并不需要每次都修改。
    // epoll 将这两个操作分开，先用 epoll_ctl() 维护等待队列，再调用 epoll_wait() 阻塞进程（解耦）。
    // 通过下图的对比显而易见，epoll 的效率得到了提升。
    //           ┌────────┐                     ┌──────┐                  ┌───────┐
    //           │ select │                     │ poll │                  │ epoll │
    //           └────────┘                     └──────┘                  └───────┘
    //               ↓                             ↓                          ↓
    //     添加待检测任务-select()         添加待检测任务-poll()           添加待检测任务
    //          阻塞-select()                  阻塞-poll()                epoll_ctrl()
    //               ↓                             ↓                          ↓
    //     添加待检测任务-select()         添加待检测任务-poll()               阻塞
    //          阻塞-select()                  阻塞-poll()                epoll_wait()
    //               ↓                             ↓                          ↓
    //     添加待检测任务-select()         添加待检测任务-poll()               阻塞
    //          阻塞-select()                  阻塞-poll()                epoll_wait()
    //                                                                        ↓
    //                                                                       阻塞
    //                                                                   epoll_wait()
    //
    // 上面添加待检测任务时和阻塞检测时 select 和 poll 都需要对检测任务容器发生拷贝。而 epoll 并不会，使用共享内存区。
    //
    // 在 epoll 中一共提供是三个 API 函数，分别处理不同的操作，函数原型如下：
    //    #include <sys/epoll.h>
    //    // 创建 epoll 实例，通过一棵红黑树管理待检测集合
    //    int epoll_create(int size);
    //    // 管理红黑树上的文件描述符(添加、修改、删除)
    //    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    //    // 检测 epoll 树中是否有就绪的文件描述符
    //    int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
    //
    // 1. int epoll_create(int size);
    //    函数的作用是创建一个红黑树模型的实例，用于管理待检测的文件描述符的集合。
    //    * 函数参数 size：在 Linux 内核 2.6.8 版本以后，这个参数是被忽略的，只需要指定一个大于 0 的数值就可以了。在那之前数字表示红黑树容器的上限。
    //    * 函数返回值：
    //      - 失败：返回-1;
    //      - 成功：返回一个有效的文件描述符，通过这个文件描述符就可以访问创建的 epoll 实例了.
    //
    // 2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    //    函数的作用是管理红黑树实例上的节点，可以进行添加、删除、修改操作。
    //    以下是函数参数涉及到的结构体类型原型：
    //
    //      // 联合体, 多个变量共用同一块内存
    //      typedef union epoll_data {
    //          void        *ptr;
    //          int          fd;    // 通常情况下使用这个成员, 和epoll_ctl的第三个参数相同即可
    //          uint32_t     u32;
    //          uint64_t     u64;
    //      } epoll_data_t;
    //
    //      struct epoll_event {
    //          uint32_t     events;      /* Epoll events */
    //          epoll_data_t data;        /* User data variable */
    //      };
    //
    //    函数参数：
    //     * int epfd：epoll_create() 函数的返回值，通过这个参数找到 epoll 实例;
    //     * int op：这是一个枚举值，控制通过该函数执行什么操作;
    //       - EPOLL_CTL_ADD：往 epoll 模型中添加新的节点;
    //       - EPOLL_CTL_MOD：修改 epoll 模型中已经存在的节点;
    //       - EPOLL_CTL_DEL：删除 epoll 模型中的指定的节点，注意删除文件描述符时，一定要先删除在关闭（close(fd)），如果删除一个已关闭的文件描述符，会返回-1，删除失败。
    //     * int fd：文件描述符，即要添加/修改/删除的文件描述符;
    //     * struct epoll_event* event：epoll事件，用来修饰第三个参数对应的文件描述符的，指定检测这个文件描述符的什么事件.
    //       struct epoll_event 字段含义如下：
    //       - uint32_t events：委托 epoll 检测的事件:
    //         * EPOLLIN：读事件, 接收数据, 检测读缓冲区，如果有数据该文件描述符就绪
    //         * EPOLLOUT：写事件, 发送数据, 检测写缓冲区，如果可写该文件描述符就绪
    //         * EPOLLERR：异常事件
    //       - epoll_data_t data：用户数据变量，这是一个联合体类型（一种共享内存的类型，用一个内存块描述不同类型，由于共享内存所以一次只允许一个类型成员存在），
    //                            通常情况下使用里边的 fd 成员，用于存储待检测的文件描述符的值，在调用 epoll_wait() 函数的时候这个值会被传出。
    //    函数返回值：
    //     * 失败：返回-1
    //     * 成功：返回0
    //
    // 3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
    //    函数的作用是检测创建的epoll实例中有没有就绪的文件描述符。
    //
    //    函数参数：
    //     * epfd：epoll_create() 函数的返回值, 通过这个参数找到 epoll 实例
    //     * events：传出参数, 这是一个结构体数组的地址, 里边存储了已就绪的文件描述符的信息,在 epfd：epoll_create 里传入的哪个参数于 fd 关联
    //     * maxevents：修饰第二个参数, 结构体数组的容量（元素个数）
    //     * timeout：如果检测的 epoll 实例中没有已就绪的文件描述符，该函数阻塞的时长, 单位 ms 毫秒
    //       - 0：函数不阻塞，不管 epoll 实例中有没有就绪的文件描述符，函数被调用后都直接返回
    //       - 大于0：如果 epoll 实例中没有已就绪的文件描述符，函数阻塞对应的毫秒数再返回
    //       - -1：函数一直阻塞，直到 epoll 实例中有已就绪的文件描述符之后才解除阻塞
    //    函数返回值：
    //     * 成功：
    //       - 等于0：函数是阻塞被强制解除了, 没有检测到满足条件的文件描述符
    //       - 大于0：检测到的已就绪的文件描述符的总个数
    //     * 失败：返回-1
    //
    // 注意上面的返回值是文件描述符的个数，而 maxevents 是 events 数组的个数。

    cout << "案例七：IO多路转接（复用）之 epoll 方案（linux 平台的）" << endl;

#ifdef __linux__
    cout << "请选择演示哪一种实现：" << endl;
    cout << "1. epoll 模型的水平触发模式（LT）单线程实现多路转接并发处理" << endl;
    cout << "2. epoll 模型的边缘触发模式（ET）单线程实现多路转接并发处理" << endl;
    cout << "3. epoll 模型的水平触发模式（LT）基于线程池复用多路转接并发处理" << endl;
    cout << "4. epoll 模型的边缘触发模式（ET）基于线程池复用多路转接并发处理" << endl;

    char commond;
    cin >> commond;
    switch (tolower(commond))
    {
    case '1':
        simpleLtEpollDemo();
        break;
    case '2':
        simpleEtEpollDemo();
        break;
    case '3':
        threadLtEpollDemo();
        break;
    case '4':
        threadEtEpollDemo();
        break;
    }
#else
    cout << "非 linux 操作系统平台不支持 epoll 模型！！可以查看 kqueue 模型以及 IOCP 模型" << endl;
#endif
}